package society.framework.core;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;

import society.framework.core.communication.Message;

/**
 * The atom class of the Society Framework. Extend this to create a new automaton.
 */
public abstract class BaseAutomaton{
	protected MessageList messageList;						//holds the messages for the transitions
	protected Message currentMessage;						//holds the last message that was removed from the list
	protected HashMap<String, State> states;				//holds the code states in the automaton
	protected String currentStateName;						//holds the name of the state in which the automaton currently is
	
	public BaseAutomaton(){
		this.messageList = new MessageList();
		this.currentMessage = null;
		this.states = new HashMap<String, State>();
		this.currentStateName = null;
	}

	protected AutomatonThread automatonThread; 		//the thread that runs the automaton
	protected boolean running = false;				//then the automaton thread is executed until running is false
	protected Callback callback = null;				//callback executed when the thread finishes
	
	/**
	 * A simple thread that runs the automaton.
	 * It may have a callback that is executed after the automaton finishes executing.
	 * @author Micu Andrei
	 *
	 */
	protected class AutomatonThread extends Thread{
		
		public AutomatonThread() {}

		@Override
		public void run() {
			BaseAutomaton.this.run();
			if(callback!=null) callback.handleMethod();		//execute callback method after stopping the while loop
		}
		
		
	}
	
	/**
	 * The method is a continuous loop with the following actions:<br/><br/>
	 * - executes the code in the current state<br/>
	 * - executes the code from the current state's transition<br/>
	 * - if the state name returned by the transition code is not null, it becomes the current state<br/>
	 * - if the state name returned by the transition code is null, the transition code is called again<br />
	 * It terminates when the automaton's running flag is set to false and the loop ends.
	 */
	public void run() {
		BaseAutomaton.State currentState;
		running = true;
		while(running){
			currentState = states.get(currentStateName);
			currentState.stateCode();								//executes the code in the state
			while((currentStateName=currentState.transition.transitionCode())==null);
		}
	}
	
	/**
	 * Method that creates and starts the automaton thread.<br/>
	 * In case the thread is created and alive it will attempt to stop it and start a new one
	 * (in this case the method returns immediately after it attempts to stop the automaton).
	 */
	public void start() {
		if(automatonThread!=null && automatonThread.isAlive()){
			this.stopSafely(new Callback() {
				
				public void handleMethod() {
					automatonThread = new AutomatonThread();
					automatonThread.setName("AutomatonThread_"+this.getClass().getSimpleName());
					automatonThread.start();
				}
				
			});
		} else {
			automatonThread = new AutomatonThread();
			automatonThread.setName("AutomatonThread_"+this.getClass().getSimpleName());
			automatonThread.start();
		}
	}

	/**
	 * Method used to safely end the thread
	 */
	public void stopSafely(){
		running = false;
		messageList.cancelRemoving();
	}
	
	/**
	 * Method used to safely end the thread. A callback method is executed after successfully stopped.
	 */
	public void stopSafely(Callback callback){
		this.callback=callback;
		running = false;
		messageList.cancelRemoving();
	}
	
	/**
	 * Adds a Message to the tail of the MessageList
	 * @param message
	 */
	public void addMessage(final Message message){
		messageList.add(message);
	}
	
	/**
	 * Gets the next message from the messageList and assigns it to the currentMessage member.
	 */
	protected void getNextMessage(){
		currentMessage = messageList.remove();
	}
	
	/**
	 * Gets the next message from the messageList and assigns it to the currentMessage member.
	 * @param timer - the amount of time to wait until the function returns. If no message is available
	 * after the timer expires then the currentMessage will be a NullMessage.
	 */
	protected void getNextMessage(int timer){
		currentMessage = messageList.remove(timer);
	}
	
	/**
	 * A State is represented by:
	 * <p>1. stateCode() -> a method which holds the code executed in this state</p>
	 * <p>2. transition -> a transition that is evaluated in order to go to the next state (this can be a Transition when there is
	 *  only one simple transition or a TransitionGroup when there are multiple possibilities)</p>
	 * @author Micu Andrei
	 *
	 */
	protected abstract class State{
		public Transition transition;

		public State() {}
		
		/**
		 * Method which holds the code executed in the containing State.
		 */
		public abstract void stateCode();
	}
	
	/**
	 * A NullState is a convenient class which represents a state that does nothing.
	 * Use this class to avoid creating redundant state classes with no code inside.
	 * @author Micu Andrei
	 *
	 */
	protected class NullState extends State{

		public NullState() {}

		@Override
		public void stateCode() {}
		
	}
	
	/**
	 * A Transition is represented by transitionCode() function 
	 * -> method that evaluates the message and returns the name of the next state.
	 * @author Micu Andrei
	 *
	 */
	protected abstract class Transition{
		
		public Transition() {}

		/**
		 * Method that performs the transition to the next state.
		 * @return - the name of the next state (if the conditions in the code are satisfied) or null (if the
		 * conditions are not satisfied)
		 */
		public abstract String transitionCode();
	}
	
	/**
	 * A SimpleTransition is a convenient class for states that have only one transition t a single state.
	 * Use this class to avoid creating redundant transition classes.  
	 * @author Micu Andrei
	 *
	 */
	protected class SimpleTransition extends Transition{
		protected final String stateName;

		public SimpleTransition(String stateName) {
			super();
			this.stateName = stateName;
		}
		
		public String transitionCode(){
			return stateName;
		}
	}
	
	/**
	 * A TransitionGroup is a convenient class used to manage indeterminism in automatons.
	 * If more than one possibilities exist you must override the transitionCode() method
	 * to return a single state name depending on the transitions in transitionsList.
	 * @author Micu Andrei
	 *
	 */
	protected class TransitionGroup extends Transition{
		public final HashMap<String, Transition> transitions = new HashMap<String, Transition>();
		
		public TransitionGroup() {}
		
		/**
		 * By default, this transitionCode() method returns the first non-null state name returned by a transition 
		 * in the transitions map. If no transition returns a string, then null is returned.
		 * @return First state name returned by a transition in the map or null if no string is returned by any.
		 */
		public String transitionCode(){
			String name;
			for(Transition transition : transitions.values()){
				name=transition.transitionCode();
				if(name!=null) return name;
			}
			return null;
		}
	}
	
	/**
	 * Method in which the Automaton state is saved and returned as String.
	 * @return - String representation of the Automaton state
	 */
	public abstract String serializeToString();
	
	/**
	 * Method in which the Automaton state is saved and written in a stream.
	 * @param outputStream - stream in which the automaton state is saved
	 * @return - true if the operation succeeded, false otherwise
	 */
	public abstract boolean serializeToStream(OutputStream outputStream);
	
	/**
	 * Method which changes the Automaton state with the one from the representation.
	 * @param serialization - the String representation of the Automaton state to be loaded
	 * @return - true if the operation succeeded, false otherwise
	 */
	public abstract boolean deserializeFromString(String serialization);
	
	/**
	 * Method which changes the Automaton state with the one from the representation.
	 * @param inputStream - a stream from which the Automaton is restored
	 * @return - true if the operation succeeded, false otherwise
	 */
	public abstract boolean deserializeFromStream(InputStream inputStream);
}
